package com.huawei.java.main;



import com.huawei.java.main.deployentity.Server;
import com.huawei.java.main.deployentity.Vm;
import com.huawei.java.main.inputentity.Require;
import com.huawei.java.main.inputentity.ServerType;
import com.huawei.java.main.inputentity.VmType;

import java.io.FileNotFoundException;
import java.util.*;
import java.util.function.Predicate;

public class Main {
    static List<Output> outputList = new ArrayList<>();//每天输出列表
    static Output dailyOutPut;

    static List<Vm> VmList =new LinkedList<>();//记录所有已经添加过的虚拟机（方便执行删除操作）
    static List<Server> serverList =new LinkedList<>();//记录所有购买的服务器

    static long totalId;//分配服务器id
    static int currentTotalBuyServerNum=0;//当前总共购买的服务器数量

    public static void main(String[] args) throws FileNotFoundException {
        long start = System.currentTimeMillis();

        //本地测试数据
        String pathName = "C:\\Users\\liuhaodong\\Desktop\\huawei-soft-compete\\training-data\\training-2.txt";
        String[] arr = Input.readFile(pathName);
        Input.resolve(arr);

        //标准输入数据
//        Input.readStdIn();

        process();

//        题目要求的最终输出结果
//        for (Output dailyOutPut : outputList) {
//            dailyOutPut.result();
//        }

        //查看统计数据
        showStatistical();

//        for (DeployServer deployServer:deployServerList){
//            int restCpu=deployServer.getNodeARestCPU()+deployServer.getNodeBRestCPU();
//            int restMem = deployServer.getNodeARestMemory()+deployServer.getNodeBRestMemory();
//            int cpu=deployServer.getCPU();
//            int mem=deployServer.getMemory();
//            System.out.println("cpu rate:"+(double)restCpu/cpu+" memory rate:"+(double)restMem/mem);
//        }


        System.out.println("耗时："+(System.currentTimeMillis()-start));
    }

    //思路参考：https://blog.csdn.net/weixin_44459972/article/details/114756490
    static void process(){
        //1.将服务器分类，先分为核内比大于1和小于1的
        List<ServerType> greaterThanOnes = new ArrayList<>();//核内比大于一，且按照costPerformance（性价比）升序排序
        List<ServerType> lessThanOnes = new ArrayList<>();//核内比小于等于一
        classifyServer(greaterThanOnes,lessThanOnes);


        //以天为单位遍历
        // TODO: 2021/3/16 暂时只看前10天的，最终应该是i<Input.dailyRequireList.size()
        for (int i=0;i<Input.dailyRequireList.size();i++){
            System.out.println("day:"+i);
            //每天一个新的output
            dailyOutPut =new Output(i,currentTotalBuyServerNum);


            List<Require> delRequireList = Input.dailyRequireList.get(i).get("delList");//每天的删除请求
            List<Require> addRequireList = Input.dailyRequireList.get(i).get("addList");//每天的add请求


            //处理每天的del请求
            for (Require delRequire:delRequireList){
                Vm delVm = null;
                for(Vm d: VmList){//找到该删除请求对应的虚拟机
                    if (d.getVmId()==delRequire.getVmId()){
                        delVm =d;
                        break;
                    }
                }
                Server server = delVm.getDeployServer();//该虚拟机部署到的服务器
                server.releaseVM(delVm);//释放资源
                VmList.remove(delVm);//删除该虚拟机
            }


//            //迁移
//            int totalMigrateNum = VmList.size()/200;//每天能够迁移的虚拟机上限
//
//            //迁移当前剩余资源最大的服务器
//            for (int index=serverList.size()-1;index>=0;index--){
//                //剩余资源占比越高越排在后面
//                serverList.sort(Comparator.comparingInt(Server::getRestResource));
//                Server server = serverList.get(index);
//                if (server.getDeployedVMList().size()>totalMigrateNum) break;
//                totalMigrateNum-= migrate(server, serverList.subList(0,index));
//            }


            //处理每天的add请求
            double averageRatio=averageRequiresRatio(addRequireList);//当日的平均核内比
            //2.将一天的请求分类为单节点部署和双节点部署，这里暂时用第一天的数据做测试
            List<Require> singleNodeList = new ArrayList<>();
            List<Require> doubleNodeList = new ArrayList<>();
            doubleOrSingle(addRequireList,singleNodeList,doubleNodeList);

            //3.分别singleNodeList和doubleNodeList执行购买并部署
            //将每天的add请求分为双节点和单节点的，再根据平均核内比分配相应的服务器。
            //有点分治的思维
            if (averageRatio>1){
                // TODO: 2021/3/16 降序还是升序？在斟酌一下
                //将当日的单、双节点的请求按照cpu降序排序
                singleNodeList.sort((o1, o2) -> o2.getVmType().getCPU()-o1.getVmType().getCPU());
                doubleNodeList.sort((o1, o2) -> o2.getVmType().getCPU()-o1.getVmType().getCPU());
                distribute(doubleNodeList,greaterThanOnes);//用核内比大于一的服务器
                distribute(singleNodeList,greaterThanOnes);

            }else {
                singleNodeList.sort((o1, o2) -> o2.getVmType().getMemory()-o1.getVmType().getMemory());
                doubleNodeList.sort((o1, o2) -> o2.getVmType().getMemory()-o1.getVmType().getMemory());
                distribute(doubleNodeList,lessThanOnes);//用核内比小于一的服务器
                distribute(singleNodeList,lessThanOnes);

            }


            //每天结束后添加每天的部署和购买服务器信息
            currentTotalBuyServerNum+=dailyOutPut.getDailyDeployServer().size();
            outputList.add(dailyOutPut);


        }
    }

    /**
     * 迁移
     * @param sourceServer 需要迁移的服务器
     * @param availableServers 可迁移的服务器，这些服务器必须是已经购买的
     * @return 消耗的迁移次数
     *
     */
    static int migrate(Server sourceServer, List<Server> availableServers){
        int count=0;
        List<Vm> vmList = sourceServer.getDeployedVMList();//待迁移的虚拟机列表
        if (vmList.size()==0) {
            int i;
        }
        for (int i= vmList.size()-1;i>=0;i--){
            Vm vm= vmList.get(i);
            for (Server targetServer : availableServers){
                if (sourceServer.equals(targetServer)) continue;
                String node = isDeployable(vm, targetServer);
                if (node==null||node.length()==0) continue;//不能部署
                //可以部署
                count++;
                sourceServer.releaseVM(vm);//从原来的服务器上删除该虚拟机
                vm.migrate(targetServer,node);//迁移该虚拟机
                targetServer.addVM(vm);//添加到新的服务器上
                vmList.remove(vm);
                break;
            }
            //如果本轮没有迁移，说明该虚拟机没有匹配的服务器可以部署
            if (i>=vmList.size()) i= vmList.size();
        }
        return count;
    }


    /**
     * 部署虚拟机
     * 外层当天请求列表，不为空进行下面的步骤
     * 内层deployServer列表，如果为空，则新增server
     * 首先从deployServer列表中找到能够部署的节点，这是现有已经购买的服务器，
     * 如果找不到这样的节点，则在serverList中遍历，找到第一个合适的server，加入到deployServer列表中（找第一个是保证了性价比最高）
     * 删除当前处理的require
     *
     * 注意，该方法结束后参数requireList应该为空！即分配完所有的请求！
     * @param requireList 需要处理的请求
     * @param serverTypeList 可选购买的服务器型号
     */
    static void distribute(List<Require> requireList, List<ServerType> serverTypeList){
        //对请求按照核内比升序排序，排在后面的核内比大于一
        requireList.sort(Comparator.comparingDouble(o -> o.getVmType().getCpuAndMemRatio()));

        //case1 遍历请求，寻找能够部署的**已有**服务器，若能够找到则部署，否则执行case2
        for (int i= requireList.size()-1;i>=0;i--){
            Require require= requireList.get(i);
            boolean state=false;
            //找到能够满足require的**已有**服务器
            serverList.sort(Comparator.comparingDouble(Server::currentCpuAndMemoryRatio));
            for (int j=serverList.size()-1;j>=0;j--){
                Server server = serverList.get(j);
                String node = isDeployable(require.getVmType(), server);
                if (node==null||node.length()==0) continue;//该服务器无法部署，继续找
                //该虚拟机请求可以部署到目标服务器
                Vm VM = new Vm(require, server,node);
                server.addVM(VM);
                VmList.add(VM);//分配完毕，添加当前虚拟机到列表中
                dailyOutPut.addDailyDeployVM(VM);
                //部署成功，删除当前请求
                requireList.remove(require);
                state=true;
                //当前请求处理完毕，处理下一个请求
                break;
            }

            if (state) continue;//表明已经找到满足的服务器了，不需要新找了，处理下一个请求

            //case2 已有服务器不能满足要求，**新购买**一台服务器
            List<Server> candidateServerList = new ArrayList<>(1000);

            //添加参数中提供的可使用的服务器类型
            for (ServerType serverType:serverTypeList)
                candidateServerList.add(new Server(generateId(),serverType));

            Server newServer = selectCandidateServer(candidateServerList,requireList);

            newServer.setPurchased(true);
            serverList.add(newServer);//添加这台新购买的服务器
            dailyOutPut.addDailyDeployServer(newServer);
            //deployVMList添加新虚拟机，然后从requireList中删除这些请求
            for (Vm vm : newServer.getDeployedVMList()){
                //添加虚拟机
                VmList.add(vm);
                dailyOutPut.addDailyDeployVM(vm);
                //删除请求，这里应该批量删除，得用iterator一个一个删
                requireList.removeIf(require1 -> vm.getVmId() == require1.getVmId());
            }

            i= requireList.size();
        }
    }

    /**
     * 算出一个最合适的服务器，以及它处理的请求
     * @param candidateServerList 候选服务器列表
     * @param requireList 需要部署到这些候选服务器中的请求
     */
    static Server selectCandidateServer(List<Server> candidateServerList, List<Require> requireList){
        List<Vm> vmList = new ArrayList<>(2000);
        for (Require require:requireList)
            vmList.add(new Vm(require,null,null));

        for (Server candidateServer : candidateServerList) {
            Iterator<Vm> vmIterator = vmList.iterator();
            while (vmIterator.hasNext()) {
                Vm vm = vmIterator.next();
                String node = isDeployable(vm, candidateServer);
                if (node == null || node.length() == 0) continue;//该请求无法部署上去
                vm.setServer(candidateServer);
                vm.setNode(node);
                candidateServer.addVM(vm);
                // TODO: 2021/3/16 从列表中删除该require (finish)
                vmIterator.remove();
            }
        }
        //如果这台候选服务器一台设备都没装就不纳入候选
        candidateServerList.removeIf(server -> server.getDeployedVMList().size() == 0);
        //将候选服务器列表按照剩余资源占比升序排序，选取第一个返回
        candidateServerList.sort(Comparator.comparingDouble(Server::getRestResourceRate));
        return candidateServerList.get(0);
    }

    /**
     * 是否可以将虚拟机部署到服务器的某个节点上
     * 单节点部署需要判断能够部署到A还是B节点，双节点部署只需要判断能不能，可以的话返回AB不可以返回null
     * @param VMType
     * @param server
     * @return A B AB null
     */
    static String isDeployable(VmType VMType, Server server){
        int nodeType = VMType.getNodeType();//当前请求双节点或单节点部署
        int requireCPU = VMType.getCPU();//当前请求所需CPU
        int requireMemory = VMType.getMemory();//当前请求所需内存

        if (nodeType==0) {//单节点部署
            int restResourcesA = server.getNodeARestCPU() + server.getNodeARestMemory();
            int restResourcesB = server.getNodeBRestCPU() + server.getNodeBRestMemory();
            if (restResourcesA >= restResourcesB) {
                if (server.getNodeARestCPU()>=requireCPU&& server.getNodeARestMemory()>=requireMemory) return "A";//部署到A节点
                if (server.getNodeBRestCPU()>=requireCPU&& server.getNodeBRestMemory()>=requireMemory) return "B";//部署到B节点
            } else {
                if (server.getNodeBRestCPU()>=requireCPU&& server.getNodeBRestMemory()>=requireMemory) return "B";//部署到B节点
                if (server.getNodeARestCPU()>=requireCPU&& server.getNodeARestMemory()>=requireMemory) return "A";//部署到A节点
            }
        }else if (nodeType==1){//双节点部署，每个节点提供需求的一半
            if (server.getNodeARestCPU()>=requireCPU/2&&
                    server.getNodeARestMemory()>=requireMemory/2&&
                    server.getNodeBRestCPU()>=requireCPU/2&&
                    server.getNodeBRestMemory()>=requireMemory/2) return "AB";
        }
        return null;//不能部署，资源不够
    }

    /**
     * 请求的平均核内比
     * @param requireList singleNodeList或doubleNodeList，已经按照cpuAndMemRatio升序排序
     */
    static double averageRequiresRatio(List<Require> requireList){
        double averageVmCoreMemRatio=0;//当天请求的平均核内比
        for (Require require:requireList){
            averageVmCoreMemRatio+=require.getVmType().getCpuAndMemRatio();
        }
        return averageVmCoreMemRatio/requireList.size();
    }

    /**
     * 奖服务器分类为核内比大于一和小于一的
     */
    static void classifyServer(List<ServerType> greaterThanOnes, List<ServerType> lessThanOnes){
        for (ServerType serverType:Input.serverMap.values()){
            if (serverType.getCpuAndMemRatio()>1) greaterThanOnes.add(serverType);
            else lessThanOnes.add(serverType);
        }
    }


    /**
     * 将oneDayList按照 VisualMachine#getNodeType()进行分类，拆成单、双节点两个列表
     * @param addOneDayList 一天的add类型请求
     * @param singleNodeList 当日请求的单节点虚拟机列表
     * @param doubleNodeList 当日请求的双节点虚拟机列表
     */
    static void doubleOrSingle(List<Require> addOneDayList,
                               List<Require> singleNodeList,List<Require> doubleNodeList){
        for(Require require:addOneDayList){
            if (require.getVmType().getNodeType()==0) singleNodeList.add(require);
            else if (require.getVmType().getNodeType()==1) doubleNodeList.add(require);
        }
    }


    /**
     * 产生购买的服务器的编号
     * @return
     */
    static long generateId(){
        return totalId++;
    }


    /**
     * 查看统计数据
     */
    static void showStatistical(){
        //查看统计数据
        long totalCost=0;//记录总花销
        for (Server server : serverList){
            totalCost = Math.addExact(totalCost, server.getHardWareCost());
        }
        for (int i=0;i< outputList.size();i++){
            System.out.println("=============day "+i+"=============");
            Output dailyOutPut=outputList.get(i);
            long dailyRestCpu=0,dailyRestMemory=0;
            long dailyCost=0;
            for (Server server : dailyOutPut.getDailyDeployServer()){
                dailyCost=Math.addExact(dailyCost, server.getDailyCost());
                dailyRestCpu=Math.addExact(dailyRestCpu, server.getNodeARestCPU()+ server.getNodeBRestCPU());
                dailyRestMemory=Math.addExact(dailyRestMemory, server.getNodeARestMemory()+ server.getNodeBRestMemory());
            }
            dailyCost = Math.multiplyExact(dailyCost,outputList.size()-i);
            totalCost=Math.addExact(totalCost,dailyCost);
            System.out.printf("当天购买服务器剩余资源统计：cpu:%d, memory:%d  当天购买的服务器总数%d，当天部署的虚拟机总数%d\n",
                    dailyRestCpu,dailyRestMemory,dailyOutPut.getDailyDeployServer().size(),dailyOutPut.getDailyDeployVM().size());

        }
        System.out.println("总花费："+totalCost);
        int totalRestCpu=0,totalRestMemory=0;
        for (Server server : serverList){//当天运行中服务器剩余资源
            totalRestCpu=Math.addExact(totalRestCpu, server.getNodeARestCPU()+ server.getNodeBRestCPU());
            totalRestMemory=Math.addExact(totalRestMemory, server.getNodeARestMemory()+ server.getNodeBRestMemory());
        }
        System.out.printf("服务器资源总剩余：cpu:%d  memory:%d",totalRestCpu,totalRestMemory);
    }
}
